package main

import (
	"archive/zip"
	"errors"
	"fmt"
	mqtt "github.com/eclipse/paho.mqtt.golang"
	"io"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime/debug"
	"strconv"
	"strings"
	"sync"
	"time"
	"yhzn/exchange-cabinet-device/cabinet"
	"yhzn/exchange-cabinet-device/mqtt-cleint"
	web_socket "yhzn/exchange-cabinet-device/web-socket"
	"zym.com/tools/zconst"
	"zym.com/tools/zconvert/json"
	"zym.com/tools/zfile"
	"zym.com/tools/zunique"
)

var (
	mqttClient mqtt_cleint.MqttClient
	yhClient   mqtt_cleint.MqttClient
)

//const (
//	SubClient         = "exchange-cabinet/client/"
//	SubClientResponse = "exchange-cabinet/client/response/"
//)
//const (
//	PubServices         = "exchange-cabinet/services"
//	PubServicesResponse = "exchange-cabinet/services/response"
//)

// topic
var (
	//topicSubscribeRpcRequest = "1492349f-4e47-49df-8999-8ba4bf4e3ab3/%s/rpcRequest"  // 平台请求主题
	//topicSubscribeResponse   = "1492349f-4e47-49df-8999-8ba4bf4e3ab3/%s/response"    // 平台回请设备上传主题
	//topicPublishMessage      = "1492349f-4e47-49df-8999-8ba4bf4e3ab3/%s/message"     // 设备主动上传主题
	//topicPublishResponse     = "1492349f-4e47-49df-8999-8ba4bf4e3ab3/%s/rpcResponse" // 设备回复平台请求主题

	topicSubscribeRpcRequest = "%s/%s/%s/rpcRequest"  // 平台请求主题
	topicSubscribeResponse   = "%s/%s/%s/response"    // 平台回请设备上传主题
	topicPublishMessage      = "%s/%s/%s/message"     // 设备主动上传主题
	topicPublishResponse     = "%s/%s/%s/rpcResponse" // 设备回复平台请求主题
)

// mqtt下行指令
const (
	DownCmdSetInterval   = "set_interval"       // 设置读取设备状态,心跳,上传状态间隔时间
	DownCmdOpen          = "open"               // 开门
	DownCmdBms           = "bms"                // 获取BMS信息
	DownCmdSetConnection = "set_connection"     // 设置API,MQTT的域名及端口
	DownCmdRest          = "reset"              // 重启
	DownCmdOperation     = "operation"          // 开启维护
	DownCmdCharge        = "charge"             // 开启或关闭充电
	DownCmdSetCabSum     = "cab_sum"            // 设置仓柜数量
	DownCmdGetCabStatus  = "get_cabinet_status" // 获取仓柜状态
	DownCmdQrcode        = "qrcode"             // 下发二维码
	DownCmdPhone         = "help_phone"         // 下发帮助电话
	DownCmdDeviceCode    = "device_code"        // 下发设备自编号
	DownUpdate           = "update"             // 程序更新
	DownDpi              = "dpi"                // 显示屏分辨率
	DownYh               = "yh"                 // 是否云禾设备
	DownGetConf          = "get_config"         // 获取配置信息
	DownSetConf          = "set_config"         // 设置配置信息
	DownCommand          = "command"            // 执行系统命令
	DownGetLogsList      = "get_logs_list"      // 获取日志文件列表
	DownGetLog           = "get_log"            // 获取日志信息
	DownUpdateArm        = "update_pcb"         // PCB板程序升级
	DownGetPcbVer        = "get_pcb_ver"        // 获取PCB板软件版本号

)

const (
	UpCmdCabinetStatus = "cabinet_status" // 上传设备状态
	UpCmdRegister      = "register"       // 设备注册
	UpCmdHeartbeat     = "heartbeat"      // 上传心跳
	UpCmdWarning       = "warning"        // 上传警报
	UpCmdChangeStatus  = "change_status"  // 状态改变
)

// REFRESH websocket指令
const (
	REFRESH = "refresh"
)

type IntervalOrder struct {
	Heartbeat     uint16 `json:"heartbeat"`
	CabinetStatus uint16 `json:"cabinet_status"`
	DeviceQuery   uint16 `json:"device_query"`
}

type ConnectOrder struct {
	ApiDomain  string `json:"api_domain"`
	ApiPort    int    `json:"api_port"`
	MqttDomain string `json:"mqtt_domain"`
	MqttPort   int    `json:"mqtt_port"`
	Prefix     string `json:"prefix"`    // mqtt topic前辍
	VendorId   string `json:"vendor_id"` // mqtt topic 厂商ID
	MqttUser   string `json:"mqtt_user"` // mqtt 户用户名
	MqttPw     string `json:"mqtt_pw"`   // mqtt 密码
}

type PublishCache struct {
	MqClient   mqtt.Client
	Topic      string
	Unix       int64
	Repetition byte
	Content    string
	Reply      bool
}

type UpdateUrl struct {
	Url string `json:"url"`
}

type Logs struct {
	Name string `json:"name"`
}

// var chanPublishList = make(chan byte, 1)
// var publishList = make(map[string]PublishCache)
var publishList sync.Map

func setPublishList(k string, v PublishCache) {
	//chanPublishList <- 1
	//publishList[k] = v
	publishList.Store(k, v)
	//<-chanPublishList
}

func getPublishList() (v map[string]PublishCache) {
	//chanPublishList <- 1
	//v = publishList
	if v == nil {
		v = make(map[string]PublishCache)
	}

	publishList.Range(func(key, value any) bool {
		v[key.(string)] = value.(PublishCache)
		return true
	})
	//<-chanPublishList
	return
}

func deletePublishList(k string) {
	//chanPublishList <- 1
	//delete(publishList, k)
	publishList.Delete(k)
	//<-chanPublishList
}

func connectYhMqtt() (e error) {
	if yhClient.ConnectHandler == nil {
		yhClient.ConnectHandler = onConnectedYh
	}
	if cfg.MqttDomain == "changecabinet.yunheznkj.com" && (cfg.MqttPort == 18835 || cfg.MqttPort == 18832) {
		return
	}
	for true {
		e = yhClient.ConnectPw(cfg.DevId+"-yh", "changecabinet.yunheznkj.com", 18835, "yhzn", "yhzn2020", true)
		if e == nil {
			//go publicTimer()
			return
		}
		time.Sleep(5 * time.Second)
	}
	return
}

func connectMqtt(loop bool) (e error) {
	if mqttClient.ConnectHandler == nil {
		mqttClient.ConnectHandler = OnConnectHandler
	}
	if mqttClient.ConnectLostHandler == nil {
		mqttClient.ConnectLostHandler = ConnectLostHandler
	}
	topicSubscribeRpcRequest = fmt.Sprintf(topicSubscribeRpcRequest, cfg.Prefix, cfg.VendorId, cfg.DevId)
	topicSubscribeResponse = fmt.Sprintf(topicSubscribeResponse, cfg.Prefix, cfg.VendorId, cfg.DevId)
	topicPublishMessage = fmt.Sprintf(topicPublishMessage, cfg.Prefix, cfg.VendorId, cfg.DevId)
	topicPublishResponse = fmt.Sprintf(topicPublishResponse, cfg.Prefix, cfg.VendorId, cfg.DevId)
	if cfg.Prefix == "" {
		topicSubscribeRpcRequest = topicSubscribeRpcRequest[1:]
		topicSubscribeResponse = topicSubscribeResponse[1:]
		topicPublishMessage = topicPublishMessage[1:]
		topicPublishResponse = topicPublishResponse[1:]
	}
	if loop {
		for {
			if !zfile.DirExists(caFile) || !zfile.DirExists(certFile) || !zfile.DirExists(keyFile) {
				e = mqttClient.ConnectPw(cfg.DevId, cfg.MqttDomain, cfg.MqttPort, cfg.MqttUser, cfg.MqttPw, true)
			} else {
				e = mqttClient.ConnectTls(cfg.MqttDomain, cfg.DevId, caFile, certFile, keyFile, cfg.MqttPort)
			}
			if e == nil {
				break
			}
			time.Sleep(5 * time.Second)
		}
	} else {
		if !zfile.DirExists(caFile) || !zfile.DirExists(certFile) || !zfile.DirExists(keyFile) {
			e = mqttClient.ConnectPw(cfg.DevId, cfg.MqttDomain, cfg.MqttPort, cfg.MqttUser, cfg.MqttPw, true)
		} else {
			e = mqttClient.ConnectTls(cfg.MqttDomain, cfg.DevId, caFile, certFile, keyFile, cfg.MqttPort)
		}
		if e != nil {
			return
		}
	}

	go publicTimer()
	//uploadRegister(mqttClient.Cli)
	return
}

func onConnectedYh(cli mqtt.Client) {
	fmt.Println("固定MQTT连接成功")

	e := yhClient.Subscribe(topicSubscribeRpcRequest, f)
	if e != nil {
		fmt.Println(e)
	}
}
func OnConnectHandler(cli mqtt.Client) {
	fmt.Println("mqtt connected success")
	netInfo.MqttConnect = true
	defer func() {
		e := subscribe()
		if e != nil {
			fmt.Println("subscribe topic fail...")
			return
		}
		fmt.Println("subscribe success")
	}()
}

func ConnectLostHandler(cli mqtt.Client, e error) {
	netInfo.MqttConnect = false
	fmt.Println("mqtt connect lost:", e)
}

var isRunPublicTimer bool

func publicTimer() {
	if isRunPublicTimer {
		return
	}
	isRunPublicTimer = true
	timer := time.NewTicker(500 * time.Millisecond)
	for {
		select {
		case <-timer.C:
			list := getPublishList()
			for k, v := range list {
				if v.Repetition == 0 {
					//_ = mqttClient.Publish(v.Topic, v.Content)
					v.MqClient.Publish(v.Topic, 0, false, v.Content)
					v.Repetition++
					setPublishList(k, v)
					continue
				}
				if v.Repetition >= 1 {
					// 发送三次都未成功
					deletePublishList(k)
					continue
				}

				if !v.Reply && time.Now().Unix()-v.Unix > int64(5*(v.Repetition+1)) {
					//_ = mqttClient.Publish(v.Topic, v.Content)
					v.MqClient.Publish(v.Topic, 0, false, v.Content)
					v.Repetition++
					setPublishList(k, v)
				}
			}
		}
	}
}

func AddPublishList(topic, ev, rid string, v interface{}, c mqtt.Client) {
	if rid == "" {
		rid = zunique.GetStrTwitterId()
	}
	o := struct {
		MqttHeart
		Data interface{} `json:"data"`
	}{
		MqttHeart: MqttHeart{
			Rid:      rid,
			DeviceId: cfg.DevId,
			Ev:       ev,
			Ct:       time.Now().Unix(),
		},
		Data: v,
	}
	if ev == UpCmdCabinetStatus {
		o.STime = devStatusTime
	}

	content, _ := json.ToJson(o)
	p := PublishCache{
		MqClient:   c,
		Repetition: 0,
		Topic:      topic,
		Content:    content,
		Unix:       time.Now().Unix(),
	}
	setPublishList(rid, p)
}

// 订阅
func subscribe() (e error) {
	fmt.Println(topicSubscribeRpcRequest)
	if e = mqttClient.Subscribe(topicSubscribeRpcRequest, f); e != nil {
		return
	}
	e = mqttClient.Subscribe(topicSubscribeResponse, f)
	return e
}

// 订阅回调
var f mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {
	defer func() {
		if p := recover(); p != nil {
			debug.PrintStack()
			fmt.Println(p)
		}
	}()

	mqttHead := MqttHeart{}
	e := json.ToObj(string(msg.Payload()), &mqttHead)
	if e != nil {
		fmt.Println("MQTT-->头解析失败：", e)
		return
	}
	if cfg.DevId != mqttHead.DeviceId {
		fmt.Println("设备ID不匹配：", cfg.DevId, mqttHead.DeviceId)
		return
	}
	switch msg.Topic() {
	case topicSubscribeRpcRequest:
		go commandHandle(mqttHead, string(msg.Payload()), client)
	case topicSubscribeResponse:
		deletePublishList(mqttHead.Rid)
	}
}

// 命令处理
func commandHandle(head MqttHeart, content string, c mqtt.Client) {
	o := struct {
		MqttHeart
		Data interface{} `json:"data"`
	}{
		MqttHeart: head,
	}
	t := false
	var operation byte = 0
	var e error
	var j string
	var ori []byte
	switch head.Ev {
	case DownCmdOpen:
		data := GeneralOrder{}
		if data, e = generalOrderAnalysis(content); e != nil {
			break
		}
		t, ori, _ = cabinet.OpenDoor(data.No)
		if data.Operation != 0 {
			operation = data.Operation
		}
	case DownCmdCharge:
		data := GeneralOrder{}
		if data, e = generalOrderAnalysis(content); e != nil {
			break
		}

		if data.Operation == 1 {
			t, ori, _ = cabinet.ChargeStart(data.No)
		} else {
			t, ori, _ = cabinet.ChargeStop(data.No)
		}
	case DownCmdBms:
		o.Data = &GetBmsOrder{}
		if e = json.ToObj(content, &o); e != nil {
			break
		}
		if o.Data.(*GetBmsOrder).No > cfg.CabinetSum || o.Data.(*GetBmsOrder).No <= 0 {
			break
		}
		publishBms(o.Data.(*GetBmsOrder).No, o.Rid, c)
		return
	case DownCmdGetCabStatus:
		data := GeneralOrder{}
		if data, e = generalOrderAnalysis(content); e != nil {
			break
		}

		if data.No == 0xff {
			var s = make([]DeviceStatus, cfg.CabinetSum)
			for i := 0; i < int(cfg.CabinetSum); i++ {
				s[i] = getDeviceStatus(byte(i + 1))
			}
			AddPublishList(topicPublishResponse, UpCmdCabinetStatus, o.Rid, s, c)
		} else {
			publishCabinetStatus(data.No, o.Rid, c)
		}
		return
	case DownCmdSetInterval:
		o.Data = &IntervalOrder{}
		if e = json.ToObj(content, &o); e != nil {
			break
		}
		if cfg.HeartbeatInterval != o.Data.(*IntervalOrder).Heartbeat && o.Data.(*IntervalOrder).Heartbeat != 0 {
			cfg.HeartbeatInterval = o.Data.(*IntervalOrder).Heartbeat
			heartbeatTimer.Stop()
			go uploadHeartbeatThread()
		}
		if cfg.UploadStatusInterval != o.Data.(*IntervalOrder).CabinetStatus && o.Data.(*IntervalOrder).CabinetStatus != 0 {
			cfg.UploadStatusInterval = o.Data.(*IntervalOrder).CabinetStatus
			uploadStatusTimer.Stop()
			go uploadStatusThread()
		}
		if cfg.DevStatusInterval != o.Data.(*IntervalOrder).DeviceQuery && o.Data.(*IntervalOrder).DeviceQuery != 0 {
			cfg.DevStatusInterval = o.Data.(*IntervalOrder).DeviceQuery
			devStatusTimer.Stop()
			go readStatusThread()
		}

		if j, e = json.ToJson(cfg); e == nil {
			zfile.WriteFile(confFile, []byte(j))
		}
		t = true
	case DownCmdSetConnection:
		o.Data = &ConnectOrder{}
		if e = json.ToObj(content, &o); e != nil {
			break
		}
		if o.Data.(*ConnectOrder).ApiDomain != "" {
			cfg.ApiDomain = o.Data.(*ConnectOrder).ApiDomain
		}
		if o.Data.(*ConnectOrder).ApiPort != 0 {
			cfg.ApiPort = o.Data.(*ConnectOrder).ApiPort
		}
		if o.Data.(*ConnectOrder).MqttDomain != "" && o.Data.(*ConnectOrder).MqttPort != 0 {
			//connectMqtt(false)
			port := o.Data.(*ConnectOrder).MqttPort
			domain := o.Data.(*ConnectOrder).MqttDomain
			var testMQ mqtt_cleint.MqttClient
			if e = testMQ.ConnectPw("test_"+cfg.DevId, domain, port, "yhzn", "yhzn2020", false); e != nil {
				t = false
				break
			}
			cfg.MqttPort = port
			cfg.MqttDomain = domain
			testMQ.Disconnect()

		}
		if o.Data.(*ConnectOrder).MqttUser != "" && o.Data.(*ConnectOrder).MqttPw != "" {
			//connectMqtt(false)
			user := o.Data.(*ConnectOrder).MqttUser
			pw := o.Data.(*ConnectOrder).MqttPw
			var testMQ mqtt_cleint.MqttClient
			if e = testMQ.ConnectPw("test_"+cfg.DevId, cfg.MqttDomain, cfg.MqttPort, user, pw, false); e != nil {
				t = false
				break
			}
			cfg.MqttUser = user
			cfg.MqttPw = pw
			testMQ.Disconnect()

		}
		//if o.Data.(*ConnectOrder).MqttPort != 0 {
		//	cfg.MqttPort = o.Data.(*ConnectOrder).MqttPort
		//}

		if o.Data.(*ConnectOrder).Prefix != "" {
			cfg.Prefix = o.Data.(*ConnectOrder).Prefix
		}
		if o.Data.(*ConnectOrder).VendorId != "" {
			cfg.VendorId = o.Data.(*ConnectOrder).VendorId
		}

		if j, e = json.ToJson(cfg); e == nil {
			zfile.WriteFile(confFile, []byte(j))
		}
		t = true
	case DownCmdRest:
		mqttAck(true, o.Ev, o.Rid, ori, operation, c)
		cmd := exec.Command("sudo", "reboot")
		_ = cmd.Run()
	case DownCmdSetCabSum:
		data := GeneralOrder{}
		if data, e = generalOrderAnalysis(content); e != nil {
			break
		}
		cfg.CabinetSum = data.Operation
		if j, e = json.ToJson(cfg); e == nil {
			zfile.WriteFile(confFile, []byte(j))
			t = true
		}
		web_socket.Write(REFRESH) // 通知web刷新页面
	case DownCmdOperation:
		web_socket.Write(head.Ev)
	case DownCmdQrcode:
		o.Data = &QRCode{}
		if e = json.ToObj(content, &o); e != nil {
			t = false
			break
		}
		cfg.QrCode = o.Data.(*QRCode).Code
		if j, e := json.ToJson(cfg); e == nil {
			zfile.WriteFile(confFile, []byte(j))
		} else {
			t = false
			break
		}
		t = true
		web_socket.Write(REFRESH) // 通知web刷新页面
	case DownCmdPhone:
		o.Data = &HelpPhone{}
		if e = json.ToObj(content, &o); e != nil {
			t = false
			break
		}
		cfg.HelpPhone = o.Data.(*HelpPhone).Phone
		if j, e := json.ToJson(cfg); e == nil {
			zfile.WriteFile(confFile, []byte(j))
		} else {
			t = false
			break
		}
		t = true
		web_socket.Write(REFRESH) // 通知web刷新页面
	case DownCmdDeviceCode:
		o.Data = &QRCode{}
		if e = json.ToObj(content, &o); e != nil {
			t = false
			break
		}
		cfg.Sn = o.Data.(*QRCode).Code
		if j, e := json.ToJson(cfg); e == nil {
			zfile.WriteFile(confFile, []byte(j))
		} else {
			t = false
			break
		}
		t = true
		web_socket.Write(REFRESH) // 通知web刷新页面
	case DownUpdateArm:
		o.Data = &UpdateUrl{}
		if e = json.ToObj(content, &o); e != nil {
			t = false
			break
		}
		updateObj := o.Data.(*UpdateUrl)
		if e = download(updateObj.Url, "pcb_update"); e != nil {
			t = false
			break
		}
		t = true
		go func() {
			devStatusTimer.Stop()
			defer func() {
				devStatusTimer.Reset(1 * time.Second)
			}()
			if e = cabinet.Update("pcb_update"); e != nil {
				mqttAck(false, o.Ev, o.Rid, ori, operation, c)
			} else {
				mqttAck(true, o.Ev, o.Rid, ori, operation, c)
			}
		}()

	case DownGetPcbVer:
		ver, e := cabinet.GetSoftwareVer()
		if e != nil {
			t = false
			break
		}
		AddPublishList(topicPublishResponse, DownGetPcbVer, o.Rid, struct {
			Ver string `json:"ver"`
		}{ver}, c)
		return
	case DownUpdate:
		o.Data = &UpdateUrl{}
		if e = json.ToObj(content, &o); e != nil {
			t = false
			break
		}
		filePath := "update"
		if !zfile.DirExists(filePath) {
			if !zfile.CreateDirectory(filePath) {
				operation = 3
				mqttAck(true, o.Ev, o.Rid, ori, operation, c)
				return
			}
		}
		downUrl := o.Data.(*UpdateUrl).Url
		zfile.WriteTextFile("log.txt", time.Now().Format(zconst.TIME_yyyyMMddHHmmss)+":收到指令。。\n")
		go func() {
			web_socket.Hint("start download...", 0)
			if e := download(downUrl, filePath+"/cabinet.zip"); e == nil {
				web_socket.Hint("download finish start unzip", 0)
				if e = unzip(filePath + "/cabinet.zip"); e == nil {
					web_socket.Hint("unzip finish please reboot device", 0)
				} else {
					operation = 2
				}
			} else {
				operation = 1
			}

			if e == nil {
				mqttAck(true, o.Ev, o.Rid, ori, operation, c)
				cmd := exec.Command("sudo", "reboot")
				_ = cmd.Run()
			} else {
				mqttAck(false, o.Ev, o.Rid, ori, operation, c)
			}
		}()
		zfile.WriteTextFile("log.txt", time.Now().Format(zconst.TIME_yyyyMMddHHmmss)+":开始下载。。\n")
		t = true
	case DownDpi:
		data := GeneralOrder{}
		if data, e = generalOrderAnalysis(content); e != nil {
			break
		}
		cfg.Dpi = int(data.Operation)
		if j, e := json.ToJson(cfg); e == nil {
			zfile.WriteFile(confFile, []byte(j))
		}
		web_socket.Write(REFRESH) // 通知web刷新页面
		t = true
	case DownYh:
		data := GeneralOrder{}
		if data, e = generalOrderAnalysis(content); e != nil {
			break
		}
		cfg.Yh = int(data.Operation)
		if j, e := json.ToJson(cfg); e == nil {
			zfile.WriteFile(confFile, []byte(j))
		}
		t = true
		web_socket.Write(REFRESH) // 通知web刷新页面
	case DownGetConf:
		AddPublishList(topicPublishResponse, UpCmdCabinetStatus, o.Rid, cfg, c)
		return
	case DownSetConf:
		o.Data = &Conf{}
		if e := json.ToObj(content, &o); e == nil {
			cfg = o.Data.(Conf)
			if writeConfig() {
				t = true
			}
		}
	case DownGetLogsList:
		files, _ := os.ReadDir("logs")
		var fileList []string
		for _, file := range files {
			fileList = append(fileList, file.Name())
		}
		AddPublishList(topicPublishResponse, "get_logs_list", o.Rid, fileList, c)
		return
	case DownGetLog:
		o.Data = &Logs{}
		if e = json.ToObj(content, &o); e != nil {
			t = false
			break
		}
		logName := o.Data.(*Logs).Name
		if content, e := zfile.ReadFile("logs/" + logName); e == nil {
			AddPublishList(topicPublishResponse, "get_log", o.Rid, string(content), c)
		}
		return
	case DownCommand:
		o.Data = &Command{}
		var ret []string
		if e := json.ToObj(content, &o); e == nil {
			strMsg := execCmd(o.Data.(*Command).Cmds)
			ret = strings.Split(strMsg, "\n")
		}

		AddPublishList(topicPublishResponse, DownCommand, o.Rid, ret, c)
		return

	}
	mqttAck(t, o.Ev, o.Rid, ori, operation, c)
}

// 常规指令解析
func generalOrderAnalysis(content string) (data GeneralOrder, e error) {
	o := struct {
		MqttHeart
		Data GeneralOrder `json:"data"`
	}{}
	if e = json.ToObj(content, &o); e != nil {
		fmt.Println("MQTT-->数据解析失败：", content)
		return
	}
	if o.Data.No > cfg.CabinetSum || o.Data.No <= 0 {
		e = errors.New("no is big")
		return
	}
	data = o.Data
	return
}

// 确认收到
func mqttAck(t bool, ev, rid string, data []byte, operation byte, c mqtt.Client) {
	o := struct {
		MqttHeart
		Data struct {
			Result    int8   `json:"result"`
			Original  string `json:"original,omitempty"`  // 原始指令
			Operation byte   `json:"operation,omitempty"` // 操作数
		} `json:"data"`
	}{}
	o.Data.Operation = operation
	if t {
		o.Data.Result = 0
	} else {
		if data != nil && len(data) == 14 {
			o.Data.Result = int8(data[len(data)-5])
			o.Data.Original = fmt.Sprintf("%x", data)
		} else {
			o.Data.Result = -1
		}
	}
	o.Ev = ev
	o.DeviceId = cfg.DevId
	o.Rid = rid
	o.Ct = time.Now().Unix()
	j, _ := json.ToJson(o)
	if token := c.Publish(topicPublishResponse, 0, false, j); token.Wait() && token.Error() != nil {
		return
	}
	return
}

// 下载
func download(url, localPath string) error {
	var (
		buf     = make([]byte, 32*1024)
		written int64
	)
	tmpFilePath := localPath + ".download"
	//创建一个http client
	client := new(http.Client)
	//client.Timeout = time.Second * 60 //设置超时时间
	//get方法获取资源
	resp, err := client.Get(url)
	if err != nil {
		return err
	}
	//读取服务器返回的文件大小
	_, err = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 32)
	if err != nil {
		return err
	}
	//创建文件
	file, err := os.Create(tmpFilePath)
	if err != nil {
		return err
	}
	defer func() {
		err = file.Close()
	}()
	if resp.Body == nil {
		return errors.New("body is null")
	}
	defer func() {
		err = resp.Body.Close()
	}()
	for {
		//读取bytes
		nr, er := resp.Body.Read(buf)
		if nr > 0 {
			//写入bytes
			nw, ew := file.Write(buf[0:nr])
			//数据长度大于0
			if nw > 0 {
				written += int64(nw)
			}
			//写入出错
			if ew != nil {
				err = ew
				break
			}
			//读取是数据长度不等于写入的数据长度
			if nr != nw {
				err = io.ErrShortWrite
				break
			}
		}
		if er != nil {
			if er != io.EOF {
				err = er
			}
			break
		}
	}
	if err == nil {
		_ = file.Close()
		err = os.Rename(tmpFilePath, localPath)
	}
	return nil
}

// 解压
func unzip(unFile string) error {
	pos2 := strings.LastIndexAny(unFile, ".")
	dst := unFile[:pos2]
	archive, err := zip.OpenReader(unFile)
	if err != nil {
		return err
	}
	defer func() {
		err = archive.Close()
	}()
	filePath := ""
	for _, f := range archive.File {
		filePath = filepath.Join(dst, f.Name)
		fmt.Println("unzipping file ", filePath)

		if !strings.HasPrefix(filePath, filepath.Clean(dst)+string(os.PathSeparator)) {
			return errors.New("invalid file path")
		}
		if f.FileInfo().IsDir() {
			_ = os.MkdirAll(filePath, os.ModePerm)
			continue
		}

		if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
			return err
		}

		dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
		if err != nil {
			return err
		}

		fileInArchive, err := f.Open()
		if err != nil {
			return err
		}

		if _, err := io.Copy(dstFile, fileInArchive); err != nil {
			return err
		}
		_ = dstFile.Close()
		_ = fileInArchive.Close()
	}
	return nil
}
